//
//  TcpServer.c
//  ThreadPool
//
//  Copyright © 2022 sinosun. All rights reserved.
//

#include "TcpServer.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <pthread.h>

#include "CFile/ThreadC.h"

int createServer()
{
    int ret = -1;
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        return ret;
    }
    
    // 2. 绑定本地端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
    // 这个宏可以代表任意一个IP地址
    // 这个宏一般用于本地的绑定操作
    addr.sin_addr.s_addr = INADDR_ANY;
    
    addr.sin_port = htons(9999); // 大端端口
    ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if (-1 == ret) {
        return ret;
    }
    
    // 3. 设置监听
    ret = listen(lfd, 128);
    
    // 4. 阻塞等待并接受客户端连接
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    int nfd = accept(lfd, (struct sockaddr*)(&cliaddr), &clilen);   // TODO:没有客户端连接，程序就会阻塞在这里
    if (-1 == nfd) {
        return ret;
    }
    
    // 打印客户端地址信息
    char ip[24] = {0,};
    printf("客户端IP:%s, 客户端PORT:%d",
           inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(cliaddr.sin_port));
    
    while (1) {
        // 接收数据
        char buffer[1024] = {0,};
        int len = recv(nfd, buffer, sizeof(buffer), 0);  // TODO:客户端不给服务端发数据，程序就会阻塞在这里
        if (len > 0) {
            printf("client say: %s \n", buffer);
            send(nfd, buffer, len, 0);                   // TODO:写缓冲区满了，程序就会阻塞在这里
        }else if (0 == len) {
            
            break;
        }
        else{
            perror("recv");
            break;
        }
    }
    
    // 5. 关闭文件描述符
    close(nfd);
    close(lfd);
    return 0;
}

typedef struct SockInfo{
    struct sockaddr_in addr;
    int fd;
} SockInfo;

SockInfo infos[512];

void* workerSendRecv(void* arg)
{
    SockInfo *info = (SockInfo*)arg;
    // 打印客户端地址信息
    char ip[24] = {0,};
    printf("客户端IP:%s, 客户端PORT:%d",
           inet_ntop(AF_INET, &info->addr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(info->addr.sin_port));
    
    while (1) {
        // 接收数据
        char buffer[1024] = {0,};
        int len = recv(info->fd, buffer, sizeof(buffer), 0);
        if (len > 0) {
            printf("client say: %s \n", buffer);
            send(info->fd, buffer, len, 0);
        }else if (0 == len) {
            
            break;
        }
        else{
            perror("recv");
            break;
        }
    }
    // 关闭通信的文件描述符
    close(info->fd);
    info->fd = -1;
        
    return NULL;
}

int createMutableServer()
{
    int ret = -1;
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        return ret;
    }
    
    // 2. 绑定本地端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
    // 这个宏可以代表任意一个IP地址
    // 这个宏一般用于本地的绑定操作
    addr.sin_addr.s_addr = INADDR_ANY;
    
    addr.sin_port = htons(9999); // 大端端口
    ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if (-1 == ret) {
        return ret;
    }
    
    // 3. 设置监听
    ret = listen(lfd, 128);
    
    // 初始化结构体数组
    int max = sizeof(infos) / sizeof(infos[0]);
    for (int i = 0; i < max; ++i) {
        bzero(&infos[i], sizeof(SockInfo));
        infos[i].fd = -1;
    }
    // 4. 阻塞等待并接受客户端连接
    int addrlen = sizeof(struct sockaddr_in);
    while (1) {
        SockInfo *pInfo;
        for (int i = 0; i < max; ++i) {
            if (-1 == infos[i].fd) {
                // 找到空闲的socket连接
                pInfo = &infos[i];
            }
        }
        
        int nfd = accept(lfd, (struct sockaddr*)(&pInfo->addr), &addrlen);
        pInfo->fd = nfd;
        if (-1 == nfd) {
            break;
        }
        
        pthread_t tid;
        pthread_create(&tid, NULL, workerSendRecv, pInfo);
        pthread_detach(tid);
    }
    
    // 5. 关闭文件描述符
    close(lfd);

    return 0;
}





/********************************************************************************************/
/*使用线程池*/
typedef struct PoolInfo
{
    ThreadPool* pool;
    int fd;
}PoolInfo;

void connWorker(void* arg)
{
    SockInfo *info = (SockInfo*)arg;
    // 打印客户端地址信息
    char ip[24] = {0,};
    printf("客户端IP:%s, 客户端PORT:%d",
           inet_ntop(AF_INET, &info->addr.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(info->addr.sin_port));
    // 不停地和客户端进行通信
    while (1) {
        // 接收数据
        char buffer[1024] = {0,};
        int len = recv(info->fd, buffer, sizeof(buffer), 0);
        if (len > 0) {
            printf("client say: %s \n", buffer);
            send(info->fd, buffer, len, 0);
        }else if (0 == len) {
            
            break;
        }
        else{
            perror("recv");
            break;
        }
    }
    // 关闭通信的文件描述符
    close(info->fd);
}

void acceptConn(void* arg)
{
    PoolInfo* poolInfo = (PoolInfo*)arg;
    
    int addrLen = sizeof(struct sockaddr_in);
    // while循环，不停地检测有没有连接过来
    while (1) {
        SockInfo* pInfo;
        pInfo = (SockInfo*)malloc(sizeof(SockInfo));
        pInfo->fd = accept(poolInfo->fd, (struct sockaddr *)&pInfo->addr, addrLen);
        if (-1 == pInfo->fd) {
            perror("accept");
            break;
        }
        
        // 添加通信的任务
        threadPoolAdd(poolInfo->pool, connWorker, pInfo);
    }
    
}

int cretaThreadPoolTcpServer()
{
    int ret = -1;
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == lfd) {
        return ret;
    }
    
    // 2. 绑定本地端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址
    // 这个宏可以代表任意一个IP地址
    // 这个宏一般用于本地的绑定操作
    addr.sin_addr.s_addr = INADDR_ANY;
    
    addr.sin_port = htons(9999); // 大端端口
    ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if (-1 == ret) {
        return ret;
    }
    
    // 3. 设置监听
    ret = listen(lfd, 128);
    
    // 4. 创建线程池
    ThreadPool* pool = threadPoolCreate(3, 5, 100);
    PoolInfo* poolInfo = (PoolInfo*)malloc(sizeof(poolInfo));
    poolInfo->pool = pool;
    poolInfo->fd = lfd;
    
    // 5.添加accept任务函数
    threadPoolAdd(pool, acceptConn, poolInfo);
                                           
    // 6. 让主线程退出
    pthread_exit(NULL);
    return ret;
}
